﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using xxpl1.HTTP.Parser;
using xxpl1.HTTP.Runtime;
using xxpl1.Log;
using xxpl1.Utils;

namespace xxpl1.GAE.Simple2
{
    public class SimpleRangeHandler2 : SimpleHandler2
    {
        private static readonly ILog logger = LogManager.GetLogger(typeof(SimpleRangeHandler2));
        private const int RangeBlock = 1000 * 1024;
        private readonly Regex rangeRegex = new Regex("bytes=\\s*(\\d+)\\s*-\\s*(\\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

        public override void Execute(HandlerContext context)
        {
            if (context.Result == HanlderReult.Ok
                && context.Response.Status != 509)
                return;

            if (!Settings.Instance.EnableRange)
            {
                context.Result = HanlderReult.NextHanler;
                return;
            }

            HttpRequest request = context.Request;
            HttpResponse response = context.Response;
            HttpWebResponse responseFromProxy = null;
            HttpWebRequest requestToProxy = null;
            int blockIndex = 0;
            try
            {
                string rangeHeader = request.GetHeader("range");
                int start = 0;
                int end = int.MaxValue;
                if (!string.IsNullOrEmpty(rangeHeader))
                {
                    Match match = rangeRegex.Match(rangeHeader);
                    if (match.Success)
                    {
                        start = int.Parse(match.Groups[1].Value);
                        end = int.Parse(match.Groups[2].Value);
                    }
                }

                requestToProxy = GetRequestToProxy(request, blockIndex, start, end);

                responseFromProxy = (HttpWebResponse)requestToProxy.GetResponse();

                ProcessHeader(responseFromProxy, response);

                ProcessResponse(responseFromProxy, response);

                responseFromProxy.Close();

                logger.InfoFormat("{0}-{1} {2} {3}ms", requestToProxy.Host, request.RawUrl, response.Status, request.ElapsedMs);

                if (response.Status == 206)
                {
                    int totalBlock = (int)Math.Ceiling((response.ContentLength - start) * 1.0 / RangeBlock);

                    while (++blockIndex < totalBlock)
                    {
                        DoRangeFetch(request, response, start, blockIndex);
                    }
                }
                context.Result = HanlderReult.Ok;
            }
            catch (Exception hex)
            {
                WebException wex = hex as WebException;
                if (wex != null && wex.Response != null)
                {
                    wex.Response.Close();
                }
                context.Result = HanlderReult.Failed;
            }
            finally
            {
                if (responseFromProxy != null)
                {
                    responseFromProxy.Close();
                }
                else
                {
                    if (requestToProxy != null)
                        requestToProxy.Abort();
                }
            }

        }

        private void DoRangeFetch(HttpRequest request, HttpResponse response, int start, int blockIndex)
        {
            HttpWebRequest requestToProxy = null;
            HttpWebResponse responseFromProxy = null;
            try
            {
                requestToProxy = GetRequestToProxy(request, blockIndex, start, response.ContentLength);

                responseFromProxy = (HttpWebResponse)requestToProxy.GetResponse();

                SkipHeader(responseFromProxy, response);

                ProcessResponse(responseFromProxy, response);

                logger.InfoFormat("{0}-{1} {2} {3}ms", requestToProxy.Host, request.RawUrl, response.Status, request.ElapsedMs);
            }
            catch (Exception ie)
            {
                WebException wex = ie as WebException;
                if (wex != null && wex.Response != null)
                {
                    wex.Response.Close();
                }
                throw;
            }
            finally
            {
                if (responseFromProxy != null)
                    responseFromProxy.Close();
                else
                {
                    if (requestToProxy != null)
                        requestToProxy.Abort();
                }
            }
        }

        protected override void ProcessHeader(HttpWebResponse responseFromProxy, HttpResponse response)
        {
            int headerSize = GetHeaderSize(responseFromProxy);

            byte[] headerBytes = ReadBytesFromStream(responseFromProxy.GetResponseStream(), headerSize);

            var ep = new XorEncryptor { Key = GetKey(responseFromProxy) };
            ep.Encrypt(headerBytes);

            string[] headers = Encoding.UTF8.GetString(headerBytes, 0, headerSize).Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

            response.Status = int.Parse(headers[0]);
            response.ContentLength = GetContentSize(responseFromProxy);

            for (int i = 1; i < headers.Length; i++)
            {
                int pos = headers[i].IndexOf(':');
                if (pos < 0) continue;

                string header = headers[i].Substring(0, pos).Trim();
                string value = headers[i].Substring(pos + 1).Trim();

                if (header.Equals("set-cookie", StringComparison.OrdinalIgnoreCase))
                {
                    ProcessCooike(response, value);
                }
                else if (header.Equals("content-length", StringComparison.OrdinalIgnoreCase))
                {
                    if (int.Parse(value) != GetContentSize(responseFromProxy))
                    {
                        logger.Error("Wrong Data Size");
                    }
                }
                else if (header.Equals("content-range", StringComparison.OrdinalIgnoreCase))
                {
                    //overide content length
                    response.ContentLength = int.Parse(value.Substring(value.IndexOf('/') + 1));
                }
                else response.AddHeader(header, value);
            }
        }

        protected virtual void SkipHeader(HttpWebResponse responseFromProxy, HttpResponse response)
        {
            int headerSize = GetHeaderSize(responseFromProxy);

            ReadBytesFromStream(responseFromProxy.GetResponseStream(), headerSize);
        }

        protected virtual HttpWebRequest GetRequestToProxy(HttpRequest request, int block, int start, int end)
        {
            var requestToProxy = (HttpWebRequest)WebRequest.Create("https://" + DnsHelper.GetHost(request.Host) + "/xpp3");
            requestToProxy.Host = DnsHelper.GetGaeHost(request.Host);
            requestToProxy.Method = "POST";
            requestToProxy.AllowAutoRedirect = false;
            requestToProxy.Proxy = EmptyProxy.Instance;
            if (Settings.Instance.ZipHeader)
                requestToProxy.Headers.Add("x-zip", "1");

            using (Stream requestStream = requestToProxy.GetRequestStream())
            {
                BlockInputStream gZipStream = Settings.Instance.ZipHeader ? new BlockInputStream(new GZipStream(requestStream, CompressionMode.Compress)) : new BlockInputStream(requestStream);
                StringBuilder sb = new StringBuilder();
                foreach (KeyValuePair<string, string> keyValuePair in request.Headers)
                {
                    if ("range".Equals(keyValuePair.Key, StringComparison.OrdinalIgnoreCase))
                        continue;
                    if (Guard.IsSafeHeader(keyValuePair.Key))
                    {
                        sb.AppendFormat("{0}:{1}\r\n", keyValuePair.Key, keyValuePair.Value);
                    }
                }
                sb.AppendFormat("range:bytes={0}-{1}\r\n", start + block * RangeBlock, Math.Min(start + (block + 1) * RangeBlock - 1, end - 1));
                gZipStream.WriteBlock(sb.ToString());
                gZipStream.WriteBlock(request.Method);
                gZipStream.WriteBlock(request.RawUrl);
                gZipStream.WriteBlock(request.RequestStream.ToArray());
                gZipStream.Close();
                requestStream.Close();
            }
            return requestToProxy;
        }
    }
}